#include "DetectThread.h"
#include "MemoryCheck.h"

DetectThread::DetectThread()
    : hEvent_(INVALID_HANDLE_VALUE)
{

}

DetectThread::~DetectThread()
{

}

bool DetectThread::init()
{
    hdPort_ = CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,0);
    if(hdPort_ == NULL){
        qInfo()<<"IoCompletionPort create failed:"<<GetLastError();
        return false;
    }
    return true;
}

bool DetectThread::addPath(QString path)
{
    if(paths_.contains(path)){
        qInfo()<<path<<"already exist.";
        return false;
    }
    HANDLE filehd;
    wchar_t wdPath[256];
    Q_ASSERT(path.length()<256);
    path.toWCharArray(wdPath);
    filehd = CreateFileW(wdPath,GENERIC_READ | GENERIC_WRITE | FILE_LIST_DIRECTORY,
                        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                        NULL,
                        OPEN_EXISTING,
                        FILE_FLAG_OVERLAPPED, //FILE_FLAG_BACKUP_SEMANTICS
                        NULL);
    if(filehd == INVALID_HANDLE_VALUE)
    {
        qInfo()<<path<<"open failed.";
        return false;
    }


    PathDetect *pd = JNew(PathDetect);
    pd->hd = filehd;
    pd->path = path;
    if(NULL == CreateIoCompletionPort(filehd,hdPort_,(ULONG_PTR)pd,0))
    {
        qInfo()<<path<<"bind ioport failed";
        CloseHandle(filehd);
        JDelete(pd);
        return false;
    }
    pd->data = JMalloc(OverlapData);
    if(false == asynReadChange(pd)){
        JFree(pd->data);
        CloseHandle(pd->hd);
        JDelete(pd);
        return false;
    }

    paths_.insert(path,dp);
    return true;
}

bool DetectThread::removePath(QString path)
{
    QMap<QString,PathDetect*>::iterator it = paths_.find(path);
    if(it == paths_.end()){
        qInfo()<<path<<"not found.";
        return false;
    }
    PathDetect* pd = it.value();
    //WSACancelAsyncRequest(pd->hd);
    JFree(pd->data);
    CloseHandle(pd->hd);
    JDelete(pd);
    paths_.erase(it);
    return true;
}

void DetectThread::run()
{
    bool ret = false;
    uint32_t bytesTransfer = 0;
    PathDetect *pd = NULL;
    OverlapData* data = NULL;
    const int timeout = 3;

    while(true)
    {
        //GetQueuedCompletionStatus 最后参数为0 不阻塞。
        ret = GetQueuedCompletionStatus(hdPort_,
                                        (LPDWORD)&bytesTransfer,
                                        (PULONG_PTR)&pd,
                                        (LPOVERLAPPED*)&data,
                                        timeout);
        if(ret == false) break;
        Q_ASSERT(pd && data);
        if(pd == NULL){
            qInfo()<<"completionkey null";
            return;
        }
        if(data == NULL){
            qInfo()<<"overlapped data null";
            return;
        }
        handleFileNotify(pd->data->buffer,bytesTransfer);
        asynReadChange(pd);
    }
}

bool DetectThread::asynReadChange(DetectThread::PathDetect *pd)
{
    wchar_t wdPath[256];
    Q_ASSERT(pd->path.length()<256);
    pd->path.toWCharArray(wdPath);
    memset(pd->data,0,sizeof(OverlapData));
    if(FALSE==ReadDirectoryChangesW(wdpath,
                                pd->data->buffer,
                                ODBBUFFERLENGTH,
                                TRUE,
                                FILE_NOTIFY_CHANGE_FILE_NAME
                                    |FILE_NOTIFY_CHANGE_DIR_NAME,
                                NULL,
                                (LPOVERLAPPED)pd->data,
                                NULL))
    {
        qInfo()<<path<<"async read change failed"<<GetLastError();
        return false;
    }
    return true;
}

void DetectThread::handleFileNotify(char *buffer, uint32_t len)
{
    PFILE_NOTIFY_INFORMATION pNotify = (PFILE_NOTIFY_INFORMATION)buffer;
    QString filename = QString::fromWCharArray(pNotify->FileName,pNotify->FileNameLength);
    switch(pNotify->Action)
    {
    case FILE_ACTION_ADDED:
        emit sgFileUpdate(filename,FUTNew);
        break;
    case FILE_ACTION_MODIFIED:
        emit sgFileUpdate(filename,FUTModify);
        break;
    case FILE_ACTION_REMOVED:
        emit sgFileUpdate(filename,FUTRemove);
        break;
    case FILE_ACTION_RENAMED_OLD_NAME:
        emit sgFileUpdate(filename,FUTRenameOld);
        if(pNotify->NextEntryOffset!=0)
        {
            DWORD offset = pNotify->NextEntryOffset;
            pNotify = (PFILE_NOTIFY_INFORMATION)(buffer+offset);
            filename = QString::fromWCharArray(pNotify->FileName,pNotify->FileNameLength);
            Q_ASSERT(pNotify->NextEntryOffset == 0);
            //do not break;
        }
        else
        {
            break;
        }
    case FILE_ACTION_RENAMED_NEW_NAME:
        emit sgFileUpdate(filename,FUTRenameNew);
        break;

    }

}

